package map;

import item.Item;
import item.Trap;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import loadsave.Index;
import loadsave.LoadSaveable;
import loadsave.Memento;
import loadsave.loadsaveobject.Iterator;
import loadsave.loadsaveobject.LoadSaveCollectionComposite;
import loadsave.loadsaveobject.LoadSaveIndex;
import loadsave.loadsaveobject.LoadSaveMapComposite;
import loadsave.loadsaveobject.LoadSaveObject;
import loadsave.loadsaveobject.LoadSaveObjectComposite;
import loadsave.loadsaveobject.LoadSavePrimitive;
import map.areaeffect.AreaEffect;
import map.areaeffect.EntityAEAssociation;
import map.terrain.Terrain;
import ai.AI;
import effects.CombatEffect;
import entity.Avatar;
import entity.BattleNPC;
import entity.Entity;
import entity.Pet;
import entity.WorldNPC;
import factory.DecalFactory;
import factory.ItemFactory;
import factory.TerrainFactory;

public class GameMap implements LoadSaveable {
	
	private String mapName;
	private int width;
	private int height;
	private Position spawn;
	
	private Terrain[][] terrain;
	private EntityAEAssociation areaEffects;
	
	private ArrayList<AI> activeAI;
	private HashMap<Entity, AI> inactiveAI;
	
	private HashMap<Position, Entity> entities;
	private HashMap<Position, Item> items;
	private HashMap<Position, Decal> decals;
	private HashMap<Position, Trap> traps;
	private HashMap<Position, CombatEffect> combatEffects;
	
	public GameMap(String mapName, int width, int height) {
		
		this.mapName = mapName;
		this.width = width;
		this.height = height;
		this.spawn = new Position(0,0);
		
		this.terrain = new Terrain[height][width];
		this.areaEffects = new EntityAEAssociation();
		
		this.activeAI = new ArrayList<AI>();
		this.inactiveAI = new HashMap<Entity, AI>();
		
		this.entities = new HashMap<Position, Entity>();
		this.items = new HashMap<Position, Item>();
		this.decals = new HashMap<Position, Decal>();
		this.traps = new HashMap<Position, Trap>();
		this.combatEffects = new HashMap<Position, CombatEffect>();
	}
	
	public GameMap() { }

	public int getWidth() {
		
		return this.width;
	}
	
	public int getHeight() {
		
		return this.height;
	}
	
	public Position getSpawn() {
		
		return this.spawn;
	}
	
	public void setSpawn(Position spawn) {
		this.spawn = spawn;
	}
	
	public String getMapName() {
		
		return this.mapName;
	}
	
	public boolean isValidPosition(Position position) {
		
		int x = position.getX();
		int y = position.getY();
		
		if(x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
			if(getTerrain(position) != null) {
				return true;
			}
		}
		return false;
	}
	
	public void updateEntity(Entity entity, Position position) {
		
		areaEffects.updateEntity(entity, position);
	}
	
	public void initializeAI() {
		
		for(Entity e : inactiveAI.keySet()) {
			AI ai = inactiveAI.get(e);
			ai.addEntity(e);
			activeAI.add(ai);
		}
	}
	
	public void uninitializeAI() {
		
		for(AI ai : activeAI) {
			ai.removeAll();
		}
		activeAI.clear();
	}
	
	//methods for checking whether objects exist on the map
	public boolean hasAreaEffect(Position position) {
		
		return areaEffects.getAreaEffect(position) != null;
	}
	
	public boolean hasCombatEffect(Position position) {
		
		return combatEffects.containsKey(position);
	}
	
	public boolean hasDecal(Position position) {
		
		return decals.containsKey(position);
	}
	
	public boolean hasEntity(Position position) {
		
		return entities.containsKey(position);
	}
	
	public boolean hasItem(Position position) {
		
		return items.containsKey(position);
	}
	
	public boolean hasTerrain(Position position) {
		if (isValidPosition(position))
			return terrain[position.getY()][position.getX()] != null;
		return false;
	}
	
	public boolean hasTrap(Position position) {
		
		return traps.containsKey(position);
	}
	
	//methods for adding objects to the map
	public void addAreaEffect(AreaEffect areaEffect, Position position) {
		
		areaEffects.addAreaEffect(areaEffect, position);
	}
	
	public void addCombatEffect(CombatEffect combatEffect, Position position) {
		
		combatEffects.put(position, combatEffect);
	}
	
	public void addDecal(Decal decal, Position position) {
		
		decals.put(position, decal);
	}
	
	public void addEntity(Entity entity, Position position) {
		
		entities.put(position, entity);
	}
	
	public void addItem(Item item, Position position) {
		
		items.put(position, item);
	}
	
	public void addTerrain(Terrain terrain, Position position) {
		
		this.terrain[position.getY()][position.getX()] = terrain;
	}
	
	public void addTrap(Trap trap, Position position) {
		
		traps.put(position, trap);
	}
	
	public void addAI(AI ai, Entity entity) {
		
		inactiveAI.put(entity, ai);
	}
	//methods for removing objects from the map
	public void removeCombatEffect(Position position) {
		
		combatEffects.remove(position);
	}
	
	public void removeEntity(Position position) {
		
		entities.remove(position);
	}
	
	public void removeAI(Entity toRemove) {
		removeEntity(toRemove.getPosition());
		for (AI ai : activeAI)
			ai.removeEntity(toRemove);
		inactiveAI.remove(toRemove);
	}
	
	public void removeItem(Position position) {
		
		items.remove(position);
	}
	
	public void removeTrap(Position position) {
		
		traps.remove(position);
	}
	
	//methods for getting objects on the map
	
	public ArrayList<AI> getAI() {
		return activeAI;
	}
	
	public AreaEffect getAreaEffect(Position position) {
		
		return areaEffects.getAreaEffect(position);
	}
	
	public EntityAEAssociation getAEAssoc() {
		
		return areaEffects;
	}
	
	public CombatEffect getCombatEffect(Position position) {
		
		return combatEffects.get(position);
	}
	
	public Decal getDecal(Position position) {
		
		return decals.get(position);
	}
	
	public Entity getEntity(Position position) {
		
		return entities.get(position);
	}
	
	public Item getItem(Position position) {
		
		return items.get(position);
	}
	
	public Terrain getTerrain(Position position) {
		
		return terrain[position.getY()][position.getX()];
	}
	
	public Trap getTrap(Position position) {
		
		return traps.get(position);
	}

	@Override
	public Memento getMemento() {
		return new Memento() {

			@Override
			public LoadSaveObject getState() {
				LoadSaveObjectComposite toReturn = new LoadSaveObjectComposite("", "GameMap");
				toReturn.add("mapName", new LoadSavePrimitive("mapName", "String", mapName));
				toReturn.add("width", new LoadSavePrimitive("width", "int", String.valueOf(width)));
				toReturn.add("height", new LoadSavePrimitive("height", "int", String.valueOf(height)));
				LoadSaveObject sp = spawn.getMemento().getState();
				sp.setName("spawn");
				toReturn.add(sp.getName(), sp);
				LoadSaveCollectionComposite tC = new LoadSaveCollectionComposite("terrain", "Terrain[][]", "Terrain");
				for (int x = 0; x < terrain.length; ++x) {
					for (int y = 0; y < terrain[x].length; ++y) {
						if (terrain[x][y] != null)
							tC.add(new LoadSaveIndex(x, y), terrain[x][y].getMemento().getState());
					}
				}
				toReturn.add(tC.getName(), tC);
				toReturn.add("areaEffects", areaEffects.getMemento().getState());
				
				LoadSaveMapComposite mC = new LoadSaveMapComposite("entities", "HashMap", "Position", "Entity");
				for (Position p : entities.keySet()) {
					mC.add(p.getMemento().getState(), entities.get(p).getMemento().getState());
				}
				toReturn.add(mC.getName(), mC);
				mC = new LoadSaveMapComposite("items", "HashMap", "Position", "Item");
				for (Position p : items.keySet()) {
					mC.add(p.getMemento().getState(), items.get(p).getMemento().getState());
				}
				toReturn.add(mC.getName(), mC);
				mC = new LoadSaveMapComposite("decals", "HashMap", "Position", "Decal");
				for (Position p : decals.keySet()) {
					mC.add(p.getMemento().getState(), decals.get(p).getMemento().getState());
				}
				toReturn.add(mC.getName(), mC);
				mC = new LoadSaveMapComposite("traps", "HashMap", "Position", "Trap");
				for (Position p : traps.keySet()) {
					mC.add(p.getMemento().getState(), traps.get(p).getMemento().getState());
				}
				toReturn.add(mC.getName(), mC);
				return toReturn;
			}

			@Override
			public void restoreState(LoadSaveObject lso) {
				mapName = lso.get("mapName").getValue();
				width = Integer.valueOf(lso.get("width").getValue());
				height = Integer.valueOf(lso.get("height").getValue());
				spawn = new Position();
				spawn.getMemento().restoreState(lso.get("spawn"));

				LoadSaveObject tC = lso.get("terrain");
//				Index i = new Index();
//				i.getMemento().restoreState(tC.get("SIZE"));
//				terrain = new Terrain[i.getIndex(0)][i.getIndex(1)];
				terrain = new Terrain[height][width];
				Iterator<LoadSaveObject> i = tC.getIterator();
				for (i.init(); i.hasNext(); i.next()) {
					Index index = new Index();
					index.getMemento().restoreState(i.getCurrKey());
					terrain[index.getIndex(0)][index.getIndex(1)] = TerrainFactory.createTerrain(i.getCurrVal().getType()); 
				}

				entities = new HashMap<Position, Entity>();
				tC = lso.get("entities");
				i = tC.getIterator();
				for (i.init(); i.hasNext(); i.next()) {
					Position p = new Position();
					p.getMemento().restoreState(i.getCurrKey());
					Map<String, Entity> entityTypes = new HashMap<String, Entity>();
					entityTypes.put("Avatar", new Avatar());
					entityTypes.put("BattleNPC", new BattleNPC());
					entityTypes.put("Pet", new Pet());
					entityTypes.put("WorldNPC", new WorldNPC());
					Entity e = entityTypes.get(i.getCurrVal().getType());
					e.getMemento().restoreState(i.getCurrVal());
					entities.put(p, e);
					GameWorld.getInstance().addEntity(e);
				}
				items = new HashMap<Position, Item>();
				tC = lso.get("items");
				i = tC.getIterator();
				for (i.init(); i.hasNext(); i.next()) {
					Position p = new Position();
					p.getMemento().restoreState(i.getCurrKey());
					items.put(p, ItemFactory.createItem(i.getCurrKey())); 
				}
				decals = new HashMap<Position, Decal>();
				tC = lso.get("decals");
				i = tC.getIterator();
				for (i.init(); i.hasNext(); i.next()) {
					Position p = new Position();
					p.getMemento().restoreState(i.getCurrKey());
					decals.put(p, DecalFactory.createDecal(i.getCurrKey().get("name").getValue())); 
				}
				traps = new HashMap<Position, Trap>();
				tC = lso.get("traps");
				i = tC.getIterator();
				for (i.init(); i.hasNext(); i.next()) {
					Position p = new Position();
					p.getMemento().restoreState(i.getCurrKey());
					traps.put(p, ItemFactory.createTrap(i.getCurrKey())); 
				}
				combatEffects = new HashMap<Position, CombatEffect>();
				areaEffects = new EntityAEAssociation();
				areaEffects.getMemento().restoreState(lso.get("areaEffects"));
			}
			
		};
	}
}
